package main

import (
	"context"
	"dash/categories"
	"dash/chatrooms"
	"dash/collectormsg"
	"dash/jwt"
	"dash/members"
	"dash/messages"
	"dash/notifications"
	"dash/topics"
	"dash/users"
	"dash/utils/httplog"
	"dash/versions"
	"dash/watch"
	"database/sql"
	"flag"
	"fmt"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/go-redis/redis/v8"
	_ "github.com/go-sql-driver/mysql"
	"github.com/olivere/elastic/v7"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var cfg struct {
	Port       int    `yaml:"port"`
	ElasticDSN string `yaml:"elasticDSN"`
	MysqlDSN   string `yaml:"mysqlDSN"`
	RedisDSN   string `yaml:"redisDSN"`
	RedisDB    int    `yaml:"redisDB"`
	FileDir    string `yaml:"fileDir"`
}

var query struct {
	DataSource       string `yaml:"dataSource"`
	ChatroomCategory string `yaml:"chatroomCategory"`
	WorkingAddress   string `yaml:"workingAddress"`
	SmartTopics      string `yaml:"smartTopics"`
	KeywordTopics    string `yaml:"keywordTopics"`
}

func init() {
	b, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		panic(err)
	}
	if err := yaml.Unmarshal(b, &cfg); err != nil {
		panic(err)
	}
	q, err := ioutil.ReadFile("query.yaml")
	if err != nil {
		panic(err)
	}
	if err := yaml.Unmarshal(q, &query); err != nil {
		panic(err)
	}
}

var printVersion = flag.Bool("v", false, "version")

func main() {
	flag.Parse()
	if *printVersion {
		fmt.Println(versions.Version)
		os.Exit(0)
	}

	var logger log.Logger
	{
		logger = log.NewJSONLogger(log.NewSyncWriter(os.Stderr))
		logger = log.With(logger, "svc", "dash")
		logger = log.With(logger, "ts", log.DefaultTimestampUTC)
		logger = log.With(logger, "caller", log.DefaultCaller)
	}

	var esClient *elastic.Client
	{
		var err error
		esClient, err = elastic.NewSimpleClient(elastic.SetURL(cfg.ElasticDSN))
		if err != nil {
			_ = level.Error(logger).Log("error", err)
			os.Exit(1)
		}
	}

	var mysqlDB *sql.DB
	{
		db, err := sql.Open("mysql", cfg.MysqlDSN)
		if err != nil {
			_ = level.Error(logger).Log("error", err.Error())
			os.Exit(1)
		}
		mysqlDB = db
	}

	var redisClient *redis.Client
	{
		client := redis.NewClient(&redis.Options{
			Network: "tcp",
			Addr:    cfg.RedisDSN,
			DB:      cfg.RedisDB,
		})
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		_, err := client.Ping(ctx).Result()
		if err != nil {
			_ = level.Error(logger).Log("error", err.Error())
			os.Exit(1)
		}
		redisClient = client
	}

	var messageRepository messages.MessageRepository
	{
		var err error
		messageRepository, err = messages.NewMessageRepository(esClient, mysqlDB, logger)
		if err != nil {
			_ = level.Error(logger).Log("error", err)
			os.Exit(1)
		}
	}

	var fileRepository messages.FileRepository
	{
		var err error
		fileRepository, err = messages.NewFileRepository(mysqlDB, esClient, logger)
		if err != nil {
			_ = level.Error(logger).Log("error", err)
			os.Exit(1)
		}
	}

	var messageService messages.Service
	{
		messageService = messages.NewService(messageRepository, fileRepository, logger, cfg.FileDir)
	}

	jwtService := jwt.NewJWTService("secret")
	userRepository := users.NewUserRepository(mysqlDB, logger)
	var userService users.UserService
	{
		userService = users.NewUserService(logger, userRepository, jwtService)
	}

	collectorUserRepository := members.NewCollectorUserRepository(mysqlDB, logger)
	var collectorUserService members.CollectorUserService
	{
		collectorUserService = members.NewCollectorUserService(collectorUserRepository)
	}

	var topicNumberService topics.NumberService
	{
		topicNumberService = topics.NewNumberService("topic_version", redisClient)
	}

	topicRepository := topics.NewRepository(mysqlDB, logger)
	var topicService topics.Service
	{
		topicService = topics.NewService(topicRepository, topicNumberService, logger)
	}

	var smartTopicService topics.SmartTopicsService
	{
		smartTopicService = topics.NewSmartTopicsService()
	}
	watchRepository := watch.NewWatchRepository(mysqlDB, redisClient, esClient, logger)
	var watchService watch.WatchService
	{
		watchService = watch.NewWatchService(watchRepository, logger)
	}
	memberRepository := members.NewMemberRepository(mysqlDB, logger, redisClient, esClient)
	var memberService members.MemberService
	{
		memberService = members.NewMemberService(logger, memberRepository, watchRepository)
	}

	categoryRepository := categories.NewCategoryRepository(mysqlDB, logger)
	categoryService := categories.NewCategoryService(categoryRepository)
	collectorRepository := collectormsg.NewCollectorRepository(mysqlDB, logger)
	keywordTopicTreeService := topics.NewKeywordTopicTreeService(topicRepository)
	organizationTreeService := collectormsg.NewOrganizationService(collectorRepository, logger)
	topQueryHandlers := collectormsg.NewQueryGroupHandlers(logger)
	{
		topQueryHandlers.AddHandler(query.DataSource, collectormsg.NewDataSourceHandler())
		topQueryHandlers.AddHandler(query.KeywordTopics, collectormsg.NewKeyworsTopicHandler(keywordTopicTreeService))
		topQueryHandlers.AddHandler(query.SmartTopics, collectormsg.NewSmartTopicsHandler(smartTopicService))
		topQueryHandlers.AddHandler(query.ChatroomCategory, collectormsg.NewCategoryHandler(categoryService))
		topQueryHandlers.AddHandler(query.WorkingAddress, collectormsg.NewWorkingAddressHandler(organizationTreeService))
	}
	topParamsService := collectormsg.NewTopParamsService(topQueryHandlers, logger)
	organizationService := collectormsg.NewOrganizationService(collectorRepository, logger)
	collectorService := collectormsg.NewCollectorService(collectorRepository, logger)

	chatroomRepository := chatrooms.NewChatroomRepository(mysqlDB, redisClient, esClient, collectorRepository, logger)
	var chatroomService chatrooms.ChatroomService
	{
		chatroomService = chatrooms.NewChatroomService(chatroomRepository, logger, categoryService, watchRepository)
	}

	notificationRepository := notifications.NewRepository(esClient, mysqlDB, "alarm_message")
	notificationService := notifications.New(notificationRepository)

	var handler http.Handler
	{
		router := http.NewServeMux()
		router.Handle("/api/v1/message/", messages.MakeHandler(messageService, logger))
		router.Handle("/api/v1/user/profile", jwt.NewJWTMiddleware(users.MakeUserHandler(userService, logger), logger, jwtService))
		router.Handle("/api/v1/user/", users.MakeLoginHandler(userService))
		router.Handle("/api/v1/member/", jwt.NewJWTMiddleware(members.MakeHandler(memberService, collectorUserService), logger, jwtService))
		router.Handle("/api/v1/chatroom/", jwt.NewJWTMiddleware(chatrooms.MakeHandler(chatroomService), logger, jwtService))
		router.Handle("/api/v1/topics/", topics.MakeHandler(topicService, smartTopicService, logger))
		//router.Handle("/api/v1/category", categories.MakeHandler(categoryService))
		router.Handle("/api/v1/query/", collectormsg.MakeQueryHandler(organizationService, topParamsService, collectorService))
		router.Handle("/api/v1/watch/", jwt.NewJWTMiddleware(watch.MakeHandler(watchService, logger), logger, jwtService))
		router.Handle("/api/v1/notification/", jwt.NewJWTMiddleware(notifications.NewHandler(notificationService), logger, jwtService))
		handler = httplog.HttpLog(logger, router)
	}

	errs := make(chan error)
	go func() {
		c := make(chan os.Signal)
		signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
		errs <- fmt.Errorf("%s", <-c)
	}()

	go func() {
		errs <- http.ListenAndServe(fmt.Sprintf(":%d", cfg.Port), handler)
	}()

	_ = logger.Log("exit", <-errs)

}
